/*
 * Code for blog.techhead.biz
 * Distributed under BSD-style license
 */

package biz.techhead.qbpos;

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.ComThread;
import com.jacob.com.Variant;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 *
 * @author jonathan
 */
public class RequestProcessorCOMImpl implements RequestProcessor {
        
    private String appId = "";
    private String appName = "";
    
    private String qbComputerName = "localhost";
    private String qbCompanyDataName = "";
    private String qbCompanyDataVersion = "7";
    private boolean qbPracticeMode = false;
    
    private ThreadPoolExecutor rpExecutor;
    
    private ThreadLocal<ActiveXComponent> qbposXMLRP = new ThreadLocal<ActiveXComponent>();
    private ThreadLocal<String> qbSessionTicket = new ThreadLocal<String>();

    public RequestProcessorCOMImpl() {
        rpExecutor = new ThreadPoolExecutor(1, 1, 120, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(), new STAThreadFactory() );
        rpExecutor.allowCoreThreadTimeOut(true);
    }

    public void setAppId(String appId) { this.appId = appId; }
    public void setAppName(String appName) { this.appName = appName; }
    
    public void setQBComputerName(String qbComputerName) { this.qbComputerName = qbComputerName; }
    public void setQBCompanyDataName(String qbCompanyDataName) { this.qbCompanyDataName = qbCompanyDataName; }
    public void setQBCompanyDataVersion(String qbCompanyDataVersion) { this.qbCompanyDataVersion = qbCompanyDataVersion; }
    public void setQBPracticeMode(boolean practice) { this.qbPracticeMode = practice; }
    
    public void setKeepAliveTime(long seconds) {
        rpExecutor.setKeepAliveTime(seconds, TimeUnit.SECONDS);
    }
    
    public <T extends ResponseProcessor> T processRequest(final String qbposXML, final T rp) {
        rpExecutor.execute( new Runnable() {
            public void run() {
                String responseXML = qbProcessRequest(qbposXML);
                rp.processResponse(responseXML);
            }
        });
        return rp;
    }

    @Override
    public String toString() {
        return getClass().getName() +
                "[appId=" + appId + ";appName=" + appName + ";" +
                qbFileName() + "]";
    }
    
    @Override
    protected void finalize() {
        rpExecutor.shutdown();
    }

    private String qbFileName() {
        String conString =
                "Computer Name=" + qbComputerName +
               ";Company Data=" + qbCompanyDataName +
               ";Version=" + qbCompanyDataVersion;
        if (qbPracticeMode) conString += ";Practice=Yes";
        return conString;
    }

    private String qbBeginSession() {
        qbInvoke("OpenConnection", appId, appName);
        return qbInvoke("BeginSession", qbFileName());
    }
    
    private String qbProcessRequest(String qbposXML) {
        return qbInvoke("ProcessRequest", qbSessionTicket.get(), qbposXML);
    }
    
    private void qbEndSession(String qbSessionTicket) {
        qbInvoke("EndSession", qbSessionTicket);
        qbInvoke("CloseConnection");
    }
    
    private String qbInvoke(String methodName, Object...args) {
        Variant[] qbArgs = new Variant[args.length];
        for (int i=0; i<args.length; i++) {
            qbArgs[i] = (args[i] instanceof Variant) ?
                (Variant) args[i] : new Variant(args[i]);
        }
        Variant ret = qbposXMLRP.get().invoke(methodName, qbArgs);
        return (ret == null) ? null : ret.toString();
    }

    private class STAThreadFactory implements ThreadFactory {
        public Thread newThread(final Runnable r) {
            Thread t = new Thread( new Runnable() {
                public void run() {
                    try {
                        ComThread.InitSTA();
                        qbposXMLRP.set( new ActiveXComponent("QBPOSXMLRPLib.RequestProcessor") );
                        String ticket = qbBeginSession();
                        qbSessionTicket.set(ticket);
                        r.run();
                        qbEndSession(ticket);
                    } finally {
                        qbposXMLRP.remove();
                        qbSessionTicket.remove();
                        ComThread.Release();
                    }
                }
            });
            t.setName(RequestProcessorCOMImpl.this.toString());
            t.setDaemon(true);
            return t;
        }
    }
}
